<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="迎着朝阳的博客" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    MyBatis源码解读(四)执行SQL |  迎着朝阳
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="https://dxysun.com/static/yan.png" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  
<script>
var _hmt = _hmt || [];
(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?aa994a8d65700b8835787dd39d079d7e";
	var s = document.getElementsByTagName("script")[0]; 
	s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-mybatisForSource4"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  MyBatis源码解读(四)执行SQL
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/05/23/mybatisForSource4/" class="article-date">
  <time datetime="2021-05-23T15:04:08.000Z" itemprop="datePublished">2021-05-23</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/mybatis/">mybatis</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.9k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">22 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p>MyBatis源码解读(四)执行SQL</p>
<a id="more"></a>



<h1 id="MyBatis源码解读之执行SQL"><a href="#MyBatis源码解读之执行SQL" class="headerlink" title="MyBatis源码解读之执行SQL"></a>MyBatis源码解读之执行SQL</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;User&gt; list = mapper.selectUserByName(<span class="string">"zhangsan"</span>);</span><br></pre></td></tr></table></figure>

<p>由于所有的Mapper都是JDK动态代理对象，所以任意的方法都是执行触发管理类MapperProxy的invoke()方法。</p>
<p>问题1：引入MapperProxy为了解决什么问题？硬编码和编译时检查问题。它需要做的事情是：根据方法查找StatementID的问题。</p>
<p>问题2：进入到invoke方法的时候做了什么事情？它是怎么找到我们要执行的SQL的？</p>
<h2 id="1、MapperProxy-invoke"><a href="#1、MapperProxy-invoke" class="headerlink" title="1、MapperProxy. invoke()"></a>1、MapperProxy. invoke()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// toString hashCode equals getClass等方法，无需走到执行SQL的流程</span></span><br><span class="line">    <span class="keyword">if</span> (Object<span class="class">.<span class="keyword">class</span>.<span class="title">equals</span>(<span class="title">method</span>.<span class="title">getDeclaringClass</span>())) </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> method.invoke(<span class="keyword">this</span>, args);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 提升获取 mapperMethod 的效率，到 MapperMethodInvoker（内部接口） 的 invoke</span></span><br><span class="line">      <span class="comment">// 普通方法会走到 PlainMethodInvoker（内部类） 的 invoke</span></span><br><span class="line">      <span class="keyword">return</span> cachedInvoker(method).invoke(proxy, method, args, sqlSession);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">    <span class="keyword">throw</span> ExceptionUtil.unwrapThrowable(t);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> MapperMethodInvoker <span class="title">cachedInvoker</span><span class="params">(Method method)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// Java8 中 Map 的方法，根据 key 获取值，如果值是 null，则把后面Object 的值赋给 key</span></span><br><span class="line">    <span class="comment">// 如果获取不到，就创建</span></span><br><span class="line">    <span class="comment">// 获取的是 MapperMethodInvoker（接口） 对象，只有一个invoke方法</span></span><br><span class="line">    <span class="comment">// 根据method 去methodCache中获取 如果返回空 则用第二个参数填充</span></span><br><span class="line">    <span class="keyword">return</span> methodCache.computeIfAbsent(method, m -&gt; &#123;</span><br><span class="line">      <span class="keyword">if</span> (m.isDefault()) &#123;</span><br><span class="line">        <span class="comment">// 接口的默认方法(Java8)，只要实现接口都会继承接口的默认方法，例如 List.sort()</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="keyword">if</span> (privateLookupInMethod == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> DefaultMethodInvoker(getMethodHandleJava8(method));</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> DefaultMethodInvoker(getMethodHandleJava9(method));</span><br><span class="line">          &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalAccessException | InstantiationException | InvocationTargetException</span><br><span class="line">            | NoSuchMethodException e) &#123;</span><br><span class="line">          <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 创建了一个 MapperMethod</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> PlainMethodInvoker(<span class="keyword">new</span> MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (RuntimeException re) &#123;</span><br><span class="line">    Throwable cause = re.getCause();</span><br><span class="line">    <span class="keyword">throw</span> cause == <span class="keyword">null</span> ? re : cause;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>1）首先判断是否需要去执行SQL，还是直接执行方法。Object本身的方法不需要去执行SQL，比如toString()、hashCode()、equals()、getClass()。</p>
<p>2）获取缓存</p>
<p>这里加入缓存是为了提升MapperMethod的获取速度，很巧妙的设计，缓存的使用在MyBatis中随处可见。</p>
<p>Map的computelfAbsent()方法：根据key获取值，如果值是null，则把后面Object的值赋给key。</p>
<p>Java8和Java9中的接口默认方法有特殊处理，返回DefaultMethodInvoker。 普通的方法返回的是PlainMethodInvoker，返回MapperMethod。</p>
<p>MapperMethod中有两个主要的属性：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// statement id （例如：com.gupaoedu.mapper.BlogMapper.selectBlogById） 和 SQL 类型</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> SqlCommand command;</span><br><span class="line"><span class="comment">// 方法签名，主要是返回值的类型</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> MethodSignature method;</span><br></pre></td></tr></table></figure>

<p>SqlCommand 封装了 statement id，例如 <code>com.dxysun.mybatis.mapper.UserMapper#selectUserByName</code> 和SQL类型。</p>
<p>MethodSignature，主要封装是返回值的类型。</p>
<p>这两个属性都是MapperMethod的内部类，另外MapperMethod种定义了多种execute()方法。</p>
<h2 id="2、MapperMethod-execute"><a href="#2、MapperMethod-execute" class="headerlink" title="2、MapperMethod. execute()"></a>2、MapperMethod. execute()</h2><p>最后会调用到mapperMethod的execute方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">execute</span><span class="params">(SqlSession sqlSession, Object[] args)</span> </span>&#123;</span><br><span class="line">    Object result;</span><br><span class="line">    <span class="keyword">switch</span> (command.getType()) &#123; <span class="comment">// 根据SQL语句的类型调用SqlSession对应的方法</span></span><br><span class="line">      <span class="keyword">case</span> INSERT: &#123;</span><br><span class="line">        <span class="comment">// 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来</span></span><br><span class="line">        Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line">        <span class="comment">// sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法</span></span><br><span class="line">        <span class="comment">// rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换</span></span><br><span class="line">        result = rowCountResult(sqlSession.insert(command.getName(), param));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">case</span> UPDATE: &#123;</span><br><span class="line">        Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line">        result = rowCountResult(sqlSession.update(command.getName(), param));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">case</span> DELETE: &#123;</span><br><span class="line">        Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line">        result = rowCountResult(sqlSession.delete(command.getName(), param));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">case</span> SELECT:</span><br><span class="line">        <span class="keyword">if</span> (method.returnsVoid() &amp;&amp; method.hasResultHandler()) &#123;</span><br><span class="line">          <span class="comment">// 返回值为空 且 ResultSet通过 ResultHandler处理的方法</span></span><br><span class="line">          executeWithResultHandler(sqlSession, args);</span><br><span class="line">          result = <span class="keyword">null</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsMany()) &#123;</span><br><span class="line">          result = executeForMany(sqlSession, args);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsMap()) &#123;</span><br><span class="line">          result = executeForMap(sqlSession, args);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsCursor()) &#123;</span><br><span class="line">          result = executeForCursor(sqlSession, args);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="comment">// 返回值为 单一对象的方法</span></span><br><span class="line">          Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line">          <span class="comment">// 普通 select 语句的执行入口 &gt;&gt;</span></span><br><span class="line">          result = sqlSession.selectOne(command.getName(), param);</span><br><span class="line">          <span class="keyword">if</span> (method.returnsOptional()</span><br><span class="line">              &amp;&amp; (result == <span class="keyword">null</span> || !method.getReturnType().equals(result.getClass()))) &#123;</span><br><span class="line">            result = Optional.ofNullable(result);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> FLUSH:</span><br><span class="line">        result = sqlSession.flushStatements();</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">default</span>:</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Unknown execution method for: "</span> + command.getName());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (result == <span class="keyword">null</span> &amp;&amp; method.getReturnType().isPrimitive() &amp;&amp; !method.returnsVoid()) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Mapper method '"</span> + command.getName()</span><br><span class="line">          + <span class="string">" attempted to return null from a method with a primitive return type ("</span> + method.getReturnType() + <span class="string">")."</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>在这一步，根据不同的type （INSERT、UPDATE、DELETE、SELECT）和返回类型：</p>
<p>1）调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。 </p>
<p>2)  调用sqlSession的insert()、update()、delete()、selectOne()方法。我们以查询为例，会走到selectOne()方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回值为 单一对象的方法</span></span><br><span class="line">Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line"><span class="comment">// 普通 select 语句的执行入口 &gt;&gt;</span></span><br><span class="line">result = sqlSession.selectOne(command.getName(), param);</span><br></pre></td></tr></table></figure>

<h2 id="3、DefaultSqlSession-selectOne"><a href="#3、DefaultSqlSession-selectOne" class="headerlink" title="3、DefaultSqlSession. selectOne()"></a>3、DefaultSqlSession. selectOne()</h2><p>这里来到了对外的接口的默认实现类DefaultSqISession。</p>
<p>selectOne() 最终也是调用了selectList()</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="function">T <span class="title">selectOne</span><span class="params">(String statement, Object parameter)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 来到了 DefaultSqlSession</span></span><br><span class="line">  <span class="comment">// Popular vote was to return null on 0 results and throw exception on too many.</span></span><br><span class="line">  List&lt;T&gt; list = <span class="keyword">this</span>.selectList(statement, parameter);</span><br><span class="line">  <span class="keyword">if</span> (list.size() == <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> list.get(<span class="number">0</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (list.size() &gt; <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> TooManyResultsException(<span class="string">"Expected one result (or null) to be returned by selectOne(), but found: "</span> + list.size());</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在SelectList() 中，先根据command name (StatementID) 从Configuration中拿到MappedStatement。</p>
<p>MappedStatement 里面有xml中增删改查标签配置的所有属性，包括id、statementType、 sqlSource、 useCache、入参、出参等等。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">selectList</span><span class="params">(String statement, Object parameter)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 为了提供多种重载（简化方法使用），和默认值</span></span><br><span class="line">  <span class="comment">// 让参数少的调用参数多的方法，只实现一次</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>.selectList(statement, parameter, RowBounds.DEFAULT);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">selectList</span><span class="params">(String statement, Object parameter, RowBounds rowBounds)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    MappedStatement ms = configuration.getMappedStatement(statement);</span><br><span class="line">    <span class="comment">// 如果 cacheEnabled = true（默认），Executor会被 CachingExecutor装饰</span></span><br><span class="line">    <span class="keyword">return</span> executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    <span class="keyword">throw</span> ExceptionFactory.wrapException(<span class="string">"Error querying database.  Cause: "</span> + e, e);</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    ErrorContext.instance().reset();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Clinton Begin</span></span><br><span class="line"><span class="comment"> * 对应的是 Mapper.xml 里面的一个增删改查的标签</span></span><br><span class="line"><span class="comment"> * 参数类型，结果类型，SQL，方法属性都在这这对象中</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MappedStatement</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> String resource;</span><br><span class="line">  <span class="keyword">private</span> Configuration configuration;</span><br><span class="line">  <span class="keyword">private</span> String id;</span><br><span class="line">  <span class="keyword">private</span> Integer fetchSize;</span><br><span class="line">  <span class="keyword">private</span> Integer timeout;</span><br><span class="line">  <span class="keyword">private</span> StatementType statementType;</span><br><span class="line">  <span class="keyword">private</span> ResultSetType resultSetType;</span><br><span class="line">  <span class="keyword">private</span> SqlSource sqlSource;</span><br><span class="line">  <span class="keyword">private</span> Cache cache;</span><br><span class="line">  <span class="keyword">private</span> ParameterMap parameterMap;</span><br><span class="line">  <span class="keyword">private</span> List&lt;ResultMap&gt; resultMaps;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">boolean</span> flushCacheRequired;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">boolean</span> useCache;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">boolean</span> resultOrdered;</span><br><span class="line">  <span class="keyword">private</span> SqlCommandType sqlCommandType;</span><br><span class="line">  <span class="keyword">private</span> KeyGenerator keyGenerator;</span><br><span class="line">  <span class="keyword">private</span> String[] keyProperties;</span><br><span class="line">  <span class="keyword">private</span> String[] keyColumns;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">boolean</span> hasNestedResultMaps;</span><br><span class="line">  <span class="keyword">private</span> String databaseId;</span><br><span class="line">  <span class="keyword">private</span> Log statementLog;</span><br><span class="line">  <span class="keyword">private</span> LanguageDriver lang;</span><br><span class="line">  <span class="keyword">private</span> String[] resultSets;</span><br><span class="line">  ...</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>然后执行了Executor的query()方法。</p>
<p>Executor是第二步openSession的时候创建的，创建了执行器基本类型之后，依次执行了二级缓存装饰，和插件拦截。</p>
<p>所以，如果有被插件拦截，这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false，再走到CachingExecutor的逻辑，然后会走到 BaseExecutor的query()方法。</p>
<h2 id="4、CachingExecutor-query-）"><a href="#4、CachingExecutor-query-）" class="headerlink" title="4、CachingExecutor. query(）"></a>4、CachingExecutor. query(）</h2><h3 id="1-创建CacheKey"><a href="#1-创建CacheKey" class="headerlink" title="(1)创建CacheKey"></a>(1)创建CacheKey</h3><p>二级缓存的CacheKey是怎么构成的呢？或者说，什么样的查询才能确定是同一个查询呢？</p>
<p>在BaseExecutor的createCacheKey方法中，用到了六个要素：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> CacheKey <span class="title">createCacheKey</span><span class="params">(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (closed) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> ExecutorException(<span class="string">"Executor was closed."</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  CacheKey cacheKey = <span class="keyword">new</span> CacheKey();</span><br><span class="line">  <span class="comment">// -1381545870:4796102018:com.dxysun.mapper.BlogMapper.selectBlogById:0:2147483647:select * from blog where bid = ?:1:development</span></span><br><span class="line">  cacheKey.update(ms.getId()); <span class="comment">// com.dxysun.mapper.BlogMapper.selectBlogById</span></span><br><span class="line">  cacheKey.update(rowBounds.getOffset()); <span class="comment">// 0</span></span><br><span class="line">  cacheKey.update(rowBounds.getLimit()); <span class="comment">// 2147483647 = 2^31-1</span></span><br><span class="line">  cacheKey.update(boundSql.getSql());</span><br><span class="line">  List&lt;ParameterMapping&gt; parameterMappings = boundSql.getParameterMappings();</span><br><span class="line">  TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();</span><br><span class="line">  <span class="comment">// mimic DefaultParameterHandler logic</span></span><br><span class="line">  <span class="keyword">for</span> (ParameterMapping parameterMapping : parameterMappings) &#123;</span><br><span class="line">    <span class="keyword">if</span> (parameterMapping.getMode() != ParameterMode.OUT) &#123;</span><br><span class="line">      Object value;</span><br><span class="line">      String propertyName = parameterMapping.getProperty();</span><br><span class="line">      <span class="keyword">if</span> (boundSql.hasAdditionalParameter(propertyName)) &#123;</span><br><span class="line">        value = boundSql.getAdditionalParameter(propertyName);</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (parameterObject == <span class="keyword">null</span>) &#123;</span><br><span class="line">        value = <span class="keyword">null</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) &#123;</span><br><span class="line">        value = parameterObject;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        MetaObject metaObject = configuration.newMetaObject(parameterObject);</span><br><span class="line">        value = metaObject.getValue(propertyName);</span><br><span class="line">      &#125;</span><br><span class="line">      cacheKey.update(value); <span class="comment">// development</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (configuration.getEnvironment() != <span class="keyword">null</span>) &#123;</span><br><span class="line">    <span class="comment">// issue #176</span></span><br><span class="line">    cacheKey.update(configuration.getEnvironment().getId());</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> cacheKey;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>也就是说，方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同，才会被认为是同一个查询。</p>
<p>CacheKey的实际值举例（toString()生成的） ，debug可以看到：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1381545870:4796102018:com.dxysun.mapper.BlogMapper.selectBlogByld:0:2147483647select*from blog where bid &#x3D;?:l:development</span><br></pre></td></tr></table></figure>

<p>注意看一下CacheKey的属性，里面有一个List按顺序存放了这些要素。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_MULTIPLIER = <span class="number">37</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_HASHCODE = <span class="number">17</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> multiplier;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> hashcode;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">long</span> checksum;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> count;</span><br><span class="line"><span class="comment">// 8/21/2017 - Sonarlint flags this as needing to be marked transient.  While true if content is not serializable, this is not always true and thus should not be marked transient.</span></span><br><span class="line"><span class="keyword">private</span> List&lt;Object&gt; updateList;</span><br></pre></td></tr></table></figure>

<p>怎么比较两个CacheKey是否相等呢？</p>
<p>如果一上来就是依次比较六个要素是否相等，就要比较6次，这样效率不高。有没有更高效的方法呢？</p>
<p>继承Obiect每个类，都有hashCode()方法，用来生成哈希码，它是用来在集合中快速判重的。</p>
<p>在生成CacheKey的时候（update方法），也更新了CacheKey的hashCode，它是用乘法哈希生成的（基数baseHashCode=17，乘法因子multiplier=37）。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public void update(Object object) &#123;</span><br><span class="line">  &#x2F;&#x2F; 加法哈希</span><br><span class="line">  int baseHashCode &#x3D; object &#x3D;&#x3D; null ? 1 : ArrayUtil.hashCode(object);</span><br><span class="line"></span><br><span class="line">  count++;</span><br><span class="line">  checksum +&#x3D; baseHashCode;</span><br><span class="line">  baseHashCode *&#x3D; count;</span><br><span class="line"></span><br><span class="line">  hashcode &#x3D; multiplier * hashcode + baseHashCode;</span><br><span class="line"></span><br><span class="line">  updateList.add(object);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Object中的hashCode() 是一个本地方法，通过随机数算法生成（OpenJDK8，默认，可以通过-XX:hashCode修改）。</p>
<p>CacheKey中的hashCode()方法进行了重写，返回自己生成的hashCode。</p>
<p>为什么要用37作为乘法因子呢？跟String中的31类似。</p>
<p>CacheKey中的equals也进行了重写，比较CacheKey是否相等。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object object)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 同一个对象</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span> == object) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 被比较的对象不是 CacheKey</span></span><br><span class="line">  <span class="keyword">if</span> (!(object <span class="keyword">instanceof</span> CacheKey)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">final</span> CacheKey cacheKey = (CacheKey) object;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// hashcode 不相等</span></span><br><span class="line">  <span class="keyword">if</span> (hashcode != cacheKey.hashcode) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// checksum 不相等</span></span><br><span class="line">  <span class="keyword">if</span> (checksum != cacheKey.checksum) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// count 不相等</span></span><br><span class="line">  <span class="keyword">if</span> (count != cacheKey.count) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; updateList.size(); i++) &#123;</span><br><span class="line">    Object thisObject = updateList.get(i);</span><br><span class="line">    Object thatObject = cacheKey.updateList.get(i);</span><br><span class="line">    <span class="keyword">if</span> (!ArrayUtil.equals(thisObject, thatObject)) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果哈希值（乘法哈希）、校验值（加法哈希）、要素个数任何一个不相等，都不是同一个查询，最后才循环比较要素，防止哈希碰撞。</p>
<p>CacheKey生成之后，调用CachingExecutor的另一个query()方法。 </p>
<h3 id="2）处理二级缓存"><a href="#2）处理二级缓存" class="headerlink" title="(2）处理二级缓存"></a>(2）处理二级缓存</h3><p>首先从MappedStatement 中取出cache对象，判断cache对象是否为空，如果为空，则没有查询二级缓存、写入二级缓存的流程。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">query</span><span class="params">(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)</span></span></span><br><span class="line"><span class="function">    <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  Cache cache = ms.getCache();</span><br><span class="line">  <span class="comment">// cache 对象是在哪里创建的？  XMLMapperBuilder类 xmlconfigurationElement()</span></span><br><span class="line">  <span class="comment">// 由 &lt;cache&gt; 标签决定</span></span><br><span class="line">  <span class="keyword">if</span> (cache != <span class="keyword">null</span>) &#123;</span><br><span class="line">    <span class="comment">// flushCache="true" 清空一级二级缓存 &gt;&gt;</span></span><br><span class="line">    flushCacheIfRequired(ms);</span><br><span class="line">    <span class="keyword">if</span> (ms.isUseCache() &amp;&amp; resultHandler == <span class="keyword">null</span>) &#123;</span><br><span class="line">      ensureNoOutParams(ms, boundSql);</span><br><span class="line">      <span class="comment">// 获取二级缓存</span></span><br><span class="line">      <span class="comment">// 缓存通过 TransactionalCacheManager、TransactionalCache 管理</span></span><br><span class="line">      <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line">      List&lt;E&gt; list = (List&lt;E&gt;) tcm.getObject(cache, key);</span><br><span class="line">      <span class="keyword">if</span> (list == <span class="keyword">null</span>) &#123;</span><br><span class="line">        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);</span><br><span class="line">        <span class="comment">// 写入二级缓存</span></span><br><span class="line">        tcm.putObject(cache, key, list); <span class="comment">// issue #578 and #116</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> list;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor</span></span><br><span class="line">  <span class="keyword">return</span> delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Cache对象是什么时候创建的呢？</p>
<p>用来解析Mapper.xml的XMLMapperBuilder类，cacheElement()方法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cacheElement(context.evalNode(&quot;cache&quot;));</span><br></pre></td></tr></table></figure>

<p>只有Mapper.xml中的<code>&lt;cache&gt;</code>标签不为空才解析。</p>
<p>二级缓存为什么要用TransactionalCacheManager来管理？</p>
<p>思考一个问题，在一个事务中：</p>
<p>1、首先插入一条数据（没有提交），此时二级缓存会被清空。</p>
<p>2、在这个事务中查询数据，写入二级缓存。</p>
<p>3、提交事务，出现异常，数据回滚。</p>
<p>此时出现了数据库没有这条数据，但是二级缓存有这条数据的情况，所以MyBatis的二级缓存需要跟事务关联起来。</p>
<p>为什么一级缓存不这么做？</p>
<p>因为一个session就是一个事务，事务回滚，会话就结束了，缓存也清空了，不存在读到一级缓存中脏数据的情况。</p>
<p>二级缓存是跨session的，也就是跨事务的，才有可能出现对同一个方法的不同事务访问。</p>
<h4 id="1）写入二级缓存"><a href="#1）写入二级缓存" class="headerlink" title="1）写入二级缓存"></a>1）写入二级缓存</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tcm.putObject(cache, key, list); <span class="comment">// issue #578 and #116</span></span><br></pre></td></tr></table></figure>

<p>从map中拿出TransactionalCache对象，把value添加到待提交的Map。此时缓存还没有真正地写入。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putObject</span><span class="params">(Object key, Object object)</span> </span>&#123;</span><br><span class="line">  entriesToAddOnCommit.put(key, object);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>只有事务提交的时候缓存才真正写入（close或者commit最后分析）。</p>
<h4 id="2）获取二级缓存"><a href="#2）获取二级缓存" class="headerlink" title="2）获取二级缓存"></a>2）获取二级缓存</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;E&gt; list = (List&lt;E&gt;) tcm.getObject(cache, key);</span><br></pre></td></tr></table></figure>

<p>从map中拿出TransactionalCache对象，这个对象也是对PerpetualCache层层装饰的缓存对象</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">getObject</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// issue #116</span></span><br><span class="line">  Object object = delegate.getObject(key);</span><br><span class="line">  <span class="keyword">if</span> (object == <span class="keyword">null</span>) &#123;</span><br><span class="line">    entriesMissedInCache.add(key);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// issue #146</span></span><br><span class="line">  <span class="keyword">if</span> (clearOnCommit) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> object;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="5、BaseExecutor-query"><a href="#5、BaseExecutor-query" class="headerlink" title="5、BaseExecutor. query()"></a>5、BaseExecutor. query()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">query</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">   <span class="comment">// 异常体系之 ErrorContext</span></span><br><span class="line">   ErrorContext.instance().resource(ms.getResource()).activity(<span class="string">"executing a query"</span>).object(ms.getId());</span><br><span class="line">   <span class="keyword">if</span> (closed) &#123;</span><br><span class="line">     <span class="keyword">throw</span> <span class="keyword">new</span> ExecutorException(<span class="string">"Executor was closed."</span>);</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> (queryStack == <span class="number">0</span> &amp;&amp; ms.isFlushCacheRequired()) &#123;</span><br><span class="line">     <span class="comment">// flushCache="true"时，即使是查询，也清空一级缓存</span></span><br><span class="line">     clearLocalCache();</span><br><span class="line">   &#125;</span><br><span class="line">   List&lt;E&gt; list;</span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">     <span class="comment">// 防止递归查询重复处理缓存</span></span><br><span class="line">     queryStack++;</span><br><span class="line">     <span class="comment">// 查询一级缓存</span></span><br><span class="line">     <span class="comment">// ResultHandler 和 ResultSetHandler的区别</span></span><br><span class="line">     list = resultHandler == <span class="keyword">null</span> ? (List&lt;E&gt;) localCache.getObject(key) : <span class="keyword">null</span>;</span><br><span class="line">     <span class="keyword">if</span> (list != <span class="keyword">null</span>) &#123;</span><br><span class="line">       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);</span><br><span class="line">     &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">       <span class="comment">// 真正的查询流程</span></span><br><span class="line">       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);</span><br><span class="line">     &#125;</span><br><span class="line">   &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">     queryStack--;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> (queryStack == <span class="number">0</span>) &#123;</span><br><span class="line">     <span class="keyword">for</span> (DeferredLoad deferredLoad : deferredLoads) &#123;</span><br><span class="line">       deferredLoad.load();</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="comment">// issue #601</span></span><br><span class="line">     deferredLoads.clear();</span><br><span class="line">     <span class="keyword">if</span> (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) &#123;</span><br><span class="line">       <span class="comment">// issue #482</span></span><br><span class="line">       clearLocalCache();</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> list;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>



<h3 id="（1）清空本地缓存"><a href="#（1）清空本地缓存" class="headerlink" title="（1）清空本地缓存"></a>（1）清空本地缓存</h3><p>queryStack用于记录查询栈，防止递归查询重复处理缓存。 flushCache=true的时候，会先清理本地缓存（一级缓存）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (queryStack == <span class="number">0</span> &amp;&amp; ms.isFlushCacheRequired()) &#123;</span><br><span class="line">  <span class="comment">// flushCache="true"时，即使是查询，也清空一级缓存</span></span><br><span class="line">  clearLocalCache();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果没有缓存，会从数据库查询：queryFromDatabase()</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 真正的查询流程</span></span><br><span class="line">list= queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);</span><br></pre></td></tr></table></figure>

<h3 id="（2）从数据库查询"><a href="#（2）从数据库查询" class="headerlink" title="（2）从数据库查询"></a>（2）从数据库查询</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">queryFromDatabase</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  List&lt;E&gt; list;</span><br><span class="line">  <span class="comment">// 先占位</span></span><br><span class="line">  localCache.putObject(key, EXECUTION_PLACEHOLDER);</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 三种 Executor 的区别，看doUpdate</span></span><br><span class="line">    <span class="comment">// 默认Simple</span></span><br><span class="line">    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// 移除占位符</span></span><br><span class="line">    localCache.removeObject(key);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 写入一级缓存</span></span><br><span class="line">  localCache.putObject(key, list);</span><br><span class="line">  <span class="keyword">if</span> (ms.getStatementType() == StatementType.CALLABLE) &#123;</span><br><span class="line">    localOutputParameterCache.putObject(key, parameter);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> list;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="a）缓存"><a href="#a）缓存" class="headerlink" title="a）缓存"></a>a）缓存</h4><p>先在缓存用占位符占为，执行查询后，移除占位符，放入数据。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 先占位</span></span><br><span class="line">localCache.putObject(key, EXECUTION_PLACEHOLDER);</span><br></pre></td></tr></table></figure>

<h4 id="b）查询"><a href="#b）查询" class="headerlink" title="b）查询"></a>b）查询</h4><p>执行Executor的doQuery（）；默认是SimpleExecutor.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 三种 Executor 的区别，看doUpdate</span></span><br><span class="line"><span class="comment">// 默认Simple</span></span><br><span class="line">list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);</span><br></pre></td></tr></table></figure>

<h2 id="6、-SimpleExecutor-doQuery"><a href="#6、-SimpleExecutor-doQuery" class="headerlink" title="6、 SimpleExecutor.doQuery()"></a>6、 SimpleExecutor.doQuery()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">doQuery</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  Statement stmt = <span class="keyword">null</span>;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    Configuration configuration = ms.getConfiguration();</span><br><span class="line">    <span class="comment">// 注意，已经来到SQL处理的关键对象 StatementHandler &gt;&gt;</span></span><br><span class="line">    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line">    <span class="comment">// 获取一个 Statement对象</span></span><br><span class="line">    stmt = prepareStatement(handler, ms.getStatementLog());</span><br><span class="line">    <span class="comment">// 执行查询</span></span><br><span class="line">    <span class="keyword">return</span> handler.query(stmt, resultHandler);</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// 用完就关闭</span></span><br><span class="line">    closeStatement(stmt);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1）创建StatementHandler"><a href="#1）创建StatementHandler" class="headerlink" title="1）创建StatementHandler"></a>1）创建StatementHandler</h3><p>在configuration.newStatementHandler() 中，new一个StatementHandler，先得到RoutingStatementHandler</p>
<p>RoutingStatementHandler里面没有任何的实现，是用来创建基本的StatementHandler的。</p>
<p>这里会根据MappedStatement里面的statementType决定 StatementHandler的类型。</p>
<p>默认是PREPARED（STATEMENT、PREPARED、 CALLABLE)。 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">RoutingStatementHandler</span><span class="params">(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// StatementType 是怎么来的？ 增删改查标签中的 statementType="PREPARED"，默认值 PREPARED</span></span><br><span class="line">  <span class="keyword">switch</span> (ms.getStatementType()) &#123;</span><br><span class="line">    <span class="keyword">case</span> STATEMENT:</span><br><span class="line">      delegate = <span class="keyword">new</span> SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> PREPARED:</span><br><span class="line">      <span class="comment">// 创建 StatementHandler 的时候做了什么？ &gt;&gt;</span></span><br><span class="line">      delegate = <span class="keyword">new</span> PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> CALLABLE:</span><br><span class="line">      delegate = <span class="keyword">new</span> CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> ExecutorException(<span class="string">"Unknown statement type: "</span> + ms.getStatementType());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。</p>
<p>这两个对象都是在上面new的时候创建的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">BaseStatementHandler</span><span class="params">(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.configuration = mappedStatement.getConfiguration();</span><br><span class="line">  <span class="keyword">this</span>.executor = executor;</span><br><span class="line">  <span class="keyword">this</span>.mappedStatement = mappedStatement;</span><br><span class="line">  <span class="keyword">this</span>.rowBounds = rowBounds;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.typeHandlerRegistry = configuration.getTypeHandlerRegistry();</span><br><span class="line">  <span class="keyword">this</span>.objectFactory = configuration.getObjectFactory();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (boundSql == <span class="keyword">null</span>) &#123; <span class="comment">// issue #435, get the key before calculating the statement</span></span><br><span class="line">    generateKeys(parameterObject);</span><br><span class="line">    boundSql = mappedStatement.getBoundSql(parameterObject);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.boundSql = boundSql;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 处理参数的ParameterHandler和处理结果集的ResultSetHandler</span></span><br><span class="line">  <span class="keyword">this</span>.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);</span><br><span class="line">  <span class="keyword">this</span>.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>这三个对象都是可以被插件拦截的四大对象之一，所以在创建之后都要用拦截器进行包装的方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> ParameterHandler <span class="title">newParameterHandler</span><span class="params">(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);</span><br><span class="line">  <span class="comment">// 植入插件逻辑（返回代理对象）</span></span><br><span class="line">  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);</span><br><span class="line">  <span class="keyword">return</span> parameterHandler;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> ResultSetHandler <span class="title">newResultSetHandler</span><span class="params">(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,</span></span></span><br><span class="line"><span class="function"><span class="params">    ResultHandler resultHandler, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  ResultSetHandler resultSetHandler = <span class="keyword">new</span> DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);</span><br><span class="line">  <span class="comment">// 植入插件逻辑（返回代理对象）</span></span><br><span class="line">  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);</span><br><span class="line">  <span class="keyword">return</span> resultSetHandler;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> StatementHandler <span class="title">newStatementHandler</span><span class="params">(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">  StatementHandler statementHandler = <span class="keyword">new</span> RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);</span><br><span class="line">  <span class="comment">// 植入插件逻辑（返回代理对象）</span></span><br><span class="line">  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);</span><br><span class="line">  <span class="keyword">return</span> statementHandler;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2）创建Statement"><a href="#2）创建Statement" class="headerlink" title="2）创建Statement"></a>2）创建Statement</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Statement <span class="title">prepareStatement</span><span class="params">(StatementHandler handler, Log statementLog)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  Statement stmt;</span><br><span class="line">  Connection connection = getConnection(statementLog);</span><br><span class="line">  <span class="comment">// 获取 Statement 对象</span></span><br><span class="line">  stmt = handler.prepare(connection, transaction.getTimeout());</span><br><span class="line">  <span class="comment">// 为 Statement 设置参数</span></span><br><span class="line">  handler.parameterize(stmt);</span><br><span class="line">  <span class="keyword">return</span> stmt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>用new出来的StatementHandler创建Statement对象，如果有插件包装，会先走到被拦截的业务逻辑。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">stmt &#x3D; handler.prepare(connection, transaction.getTimeout());</span><br></pre></td></tr></table></figure>

<p>prepareStatement() 方法对语句进行预编译，处理参数</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">handler.parameterize(stmt);</span><br></pre></td></tr></table></figure>

<p>这里面会调用parameterHandler设置参数，如果有插件包装，会先走到被拦截的业务逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">parameterize</span><span class="params">(Statement statement)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  parameterHandler.setParameters((PreparedStatement) statement);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3）执行的StatementHandler的query-方法"><a href="#3）执行的StatementHandler的query-方法" class="headerlink" title="3）执行的StatementHandler的query() 方法"></a>3）执行的StatementHandler的query() 方法</h3><p>执行RoutingStatementHandler的query()方法，deleqate委派，最终执行PreparedStatementHandler的query()方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;E&gt; <span class="function">List&lt;E&gt; <span class="title">query</span><span class="params">(Statement statement, ResultHandler resultHandler)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  PreparedStatement ps = (PreparedStatement) statement;</span><br><span class="line">  ps.execute();</span><br><span class="line">  <span class="keyword">return</span> resultSetHandler.handleResultSets(ps);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4）执行PreparedStatement的execute-方法"><a href="#4）执行PreparedStatement的execute-方法" class="headerlink" title="4）执行PreparedStatement的execute()方法"></a>4）执行PreparedStatement的execute()方法</h3><p>后面就是JDBC包中的PreparedStatement的执行了。</p>
<h3 id="5）-ResultSetHandler处理结果"><a href="#5）-ResultSetHandler处理结果" class="headerlink" title="5） ResultSetHandler处理结果"></a>5） ResultSetHandler处理结果</h3><p>如果有插件包装，会先走到被拦截的业务逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> resultSetHandler.handleResultSets(ps);</span><br></pre></td></tr></table></figure>

<p>怎么把ResultSet转换成<code>List&lt;Object&gt;</code>？</p>
<p>ResultSetHandler 只有一个实现类： DefaultResultSetHandler。也就是执行DefaultResultSetHandler的handleResultSets()方法。 </p>
<p>首先会先拿到第一个结果集，如果没有配置一个查询返回多个结果集的情况，一般只有一个结果集。</p>
<p>如果下面的这个while循环我们也不用，就是执行一次。</p>
<p>然后会调用handleResultSet()方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">handleResultSets</span><span class="params">(Statement stmt)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">  ErrorContext.instance().activity(<span class="string">"handling results"</span>).object(mappedStatement.getId());</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 该集合用于保存映射结果集得到的结果对象</span></span><br><span class="line">  <span class="keyword">final</span> List&lt;Object&gt; multipleResults = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">int</span> resultSetCount = <span class="number">0</span>;</span><br><span class="line">  <span class="comment">// 获取第一个ResultSet对象</span></span><br><span class="line">  ResultSetWrapper rsw = getFirstResultSet(stmt);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// &lt;resultMap&gt;会被解析为 ResultMap对象，并保存到 mappedStatement.resultMaps 集合中</span></span><br><span class="line">  <span class="comment">// 如果SQL节点能够产生多个ResultSet，那么我们可以在SQL节点的 resultMap属性中配置多个&lt;resultMap&gt;节点的id</span></span><br><span class="line">  <span class="comment">// 他们之间通过','分割，实现对多个结果集的映射</span></span><br><span class="line">  List&lt;ResultMap&gt; resultMaps = mappedStatement.getResultMaps();</span><br><span class="line">  <span class="comment">// 获取 resultMap的个数</span></span><br><span class="line">  <span class="keyword">int</span> resultMapCount = resultMaps.size();</span><br><span class="line">  <span class="comment">// 验证 如果结果集不为空 那么 resultMaps不能为空，否则抛异常</span></span><br><span class="line">  validateResultMapsCount(rsw, resultMapCount);</span><br><span class="line">  <span class="comment">// 遍历 resultMaps 集合</span></span><br><span class="line">  <span class="keyword">while</span> (rsw != <span class="keyword">null</span> &amp;&amp; resultMapCount &gt; resultSetCount) &#123;</span><br><span class="line">    <span class="comment">// 获取循环的 ResultMap对象</span></span><br><span class="line">    ResultMap resultMap = resultMaps.get(resultSetCount);</span><br><span class="line">    <span class="comment">// 处理单个ResultSet对象 也就是根据resultMap中定义的映射规则对ResultSet进行映射</span></span><br><span class="line">    <span class="comment">// 并将结果添加到 multipleResults 中  对单个结果集的映射</span></span><br><span class="line">    handleResultSet(rsw, resultMap, multipleResults, <span class="keyword">null</span>);</span><br><span class="line">    rsw = getNextResultSet(stmt);<span class="comment">// 获取下一个结果集</span></span><br><span class="line">    cleanUpAfterHandlingResultSet(); <span class="comment">// 清空 nestedResultObjects</span></span><br><span class="line">    resultSetCount++;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// resultSets 该属性仅仅对多结果集的情况适用，该属性将列出语句执行后返回的结果集，并给每个结果集一个名称</span></span><br><span class="line">  <span class="comment">// 名称是逗号分隔的  可以先不关注</span></span><br><span class="line">  String[] resultSets = mappedStatement.getResultSets();</span><br><span class="line">  <span class="keyword">if</span> (resultSets != <span class="keyword">null</span>) &#123;</span><br><span class="line">    <span class="keyword">while</span> (rsw != <span class="keyword">null</span> &amp;&amp; resultSetCount &lt; resultSets.length) &#123;</span><br><span class="line">      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);</span><br><span class="line">      <span class="keyword">if</span> (parentMapping != <span class="keyword">null</span>) &#123;</span><br><span class="line">        String nestedResultMapId = parentMapping.getNestedResultMapId();</span><br><span class="line">        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);</span><br><span class="line">        handleResultSet(rsw, resultMap, <span class="keyword">null</span>, parentMapping);</span><br><span class="line">      &#125;</span><br><span class="line">      rsw = getNextResultSet(stmt);</span><br><span class="line">      cleanUpAfterHandlingResultSet();</span><br><span class="line">      resultSetCount++;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> collapseSingleResultList(multipleResults);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><img src="https://tu.dxysun.com/04-MapperProxy-20210524003005.png" alt=""></p>
<h1 id="MyBatis-核心对象"><a href="#MyBatis-核心对象" class="headerlink" title="MyBatis 核心对象"></a>MyBatis 核心对象</h1><table>
<thead>
<tr>
<th>对象</th>
<th>相关对象</th>
<th>作用</th>
</tr>
</thead>
<tbody><tr>
<td>Configuration</td>
<td>MapperRegistry<br>TypeAliasRegistry<br/>TypeHandlerRegistry</td>
<td>包含了MyBatis的所有的配置信息</td>
</tr>
<tr>
<td>SqlSession</td>
<td>SqlSessionFactory<br>DefaultSqlSession</td>
<td>对操作数据库的增删改查的API进行了封装，提供给应用层使用</td>
</tr>
<tr>
<td>BaseExecutor</td>
<td>SimpleExecutor<br>BatchExecutor<br>ReuseExecutor</td>
<td>MyBatis执行器，是MyBatis调度的核心，负责SQL语句的生成和查查询缓存的维护</td>
</tr>
<tr>
<td>StatementHandler</td>
<td>BaseStatementHandler<br>SimpleStatementHandler<br/>PreparedStatementHandler<br/>CallableStatementHandler</td>
<td>封装了JDBCStatement操作，负责对JDBCstatement的操作，如设置参数、将Statement结果集转换成List集合</td>
</tr>
<tr>
<td>ParameterHandler</td>
<td>DefaultParameterHandler</td>
<td>把用户传递的参数转换成JDBC Statement所需要的参数</td>
</tr>
<tr>
<td>ResultSetHandler</td>
<td>DefaultResultSetHandler</td>
<td>把JDBC返回的ResultSet结果集对象转换成List类型的集合</td>
</tr>
<tr>
<td>MapperProxy</td>
<td>MapperProxyFactory</td>
<td>触发管理类，用于代理Mapper接口方法</td>
</tr>
<tr>
<td>MappedStatement</td>
<td>SqlSource<br/>BoundSql</td>
<td>MappedStatement维护了一条&lt;select|update|delete|insert&gt;节点的封装，表示一条SQL包括了SQL信息、入参信息、出参信息</td>
</tr>
</tbody></table>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://dxysun.com/2021/05/23/mybatisForSource4/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mybatis/" rel="tag">mybatis</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/08/22/mysqlForPrinciple1/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Mysql架构与执行流程
          
        </div>
      </a>
    
    
      <a href="/2021/05/23/mybatisForSource3/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">MyBatis源码解读(三)获取Mapper对象</div>
      </a>
    
  </nav>

  
   
  
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2024
        <i class="ri-heart-fill heart_icon"></i> dxysun
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="https://beian.miit.gov.cn" target="_black" rel="nofollow">豫ICP备17012675号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="https://dxysun.com/static/logo.png" alt="迎着朝阳"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/photos">相册</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/alipay-20201219151322.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/weixin-20201219151346.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
  var ayerConfig = {
    mathjax: true
  }
</script>

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


<script src="/js/dz.js"></script>



    
  </div>
</body>

</html>